﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;


namespace Highlearner_API
{
	public enum ERetrievedState
	{
		Retrieved = 0,
		UpdateNeeded = 1,
		NotRetrieved = 2
	}

	[Serializable]
	public class CFolderItem : ISerializable
	{
		public String Name;

		private bool m_bDownload = true;
		public bool Download
		{
			get
			{
				return m_bDownload;
			}
			set
			{
				CFileItem l_oFile;
				CFolderItem l_oFolder;

				m_bDownload = value;

				for (int i = 0; i < Files.Count; i++)
				{
					l_oFile = Files.Values[i];
					l_oFile.Download = value;
				}
				for (int i = 0; i < Folders.Count; i++)
				{
					l_oFolder = Folders.Values[i];
					l_oFolder.Download = value;
				}
			}
		}

		public CCourse Course;
		public CFolderItem Parent;

		public SortedList<String, CFolderItem> Folders = new SortedList<String, CFolderItem>();
		public SortedList<String, CFileItem> Files = new SortedList<String, CFileItem>();

		public CFolderItem(String a_sFolderName, CFolderItem a_oParent, CCourse a_oCourse)
		{
			Name = a_sFolderName;
			Parent = a_oParent;
			Course = a_oCourse;
		}

		//Deserialization constructor.
		public CFolderItem(SerializationInfo a_oInfo, StreamingContext a_oContext)
		{
			Name = (String)a_oInfo.GetValue("Name", typeof(String));
			Download = (Boolean)a_oInfo.GetValue("Download", typeof(Boolean));
			Course = (CCourse)a_oInfo.GetValue("Course", typeof(CCourse));

			Folders = (SortedList<String, CFolderItem>)a_oInfo.GetValue("Folders", typeof(SortedList<String, CFolderItem>));
			Files = (SortedList<String, CFileItem>)a_oInfo.GetValue("Files", typeof(SortedList<String, CFileItem>));
		}

		//Serialization function.
		public void GetObjectData(SerializationInfo a_oInfo, StreamingContext a_oContext)
		{
			a_oInfo.AddValue("Name", Name);
			a_oInfo.AddValue("Download", Download);

			a_oInfo.AddValue("Course", Course);

			a_oInfo.AddValue("Folders", Folders, typeof(SortedList<String, CFolderItem>));
			a_oInfo.AddValue("Files", Files, typeof(SortedList<String, CFileItem>));
		}

		public string GetPath()
		{
			string l_sPath = "";
			CFolderItem l_oCurrentFolder = this;

			while (l_oCurrentFolder.Parent != null)
			{
				l_sPath = Path.DirectorySeparatorChar + l_oCurrentFolder.Name + l_sPath;
				l_oCurrentFolder = l_oCurrentFolder.Parent;
			}

			return Course.Name + l_sPath;
		}

		public override String ToString()
		{
			return Name;
		}

		public CFolderItem GetFolder(String Path)
		{
			String[] l_asFolders = Path.Split('\\');

			CFolderItem CurrentFolder = this;

			foreach (String l_sFolder in l_asFolders)
			{
				CurrentFolder = CurrentFolder.Folders[l_sFolder];
			}

			return CurrentFolder;
		}
		public int CountFiles(bool a_bIncludeInnerFolders, bool a_bDownloadEnabled)
		{
			int l_iCount = 0;

			if (a_bIncludeInnerFolders)
			{
				foreach (CFolderItem l_oFolder in Folders.Values)
				{
					l_iCount += l_oFolder.CountFiles(a_bIncludeInnerFolders, a_bDownloadEnabled);
				}
			}

			if (a_bDownloadEnabled)
			{
				foreach (CFileItem l_oFile in Files.Values)
				{
					if (l_oFile.Download)
					{
						l_iCount++;
					}
				}

				return l_iCount;
			}
			else
			{
				return l_iCount + Files.Count;
			}
		}
	}

	[Serializable]
	public class CFileItem : ISerializable
	{
		public String Name;
		public bool Download = true;

		public CFolderItem Folder;
		public CCourse Course;

		public CFileItem(String a_sFileName, CFolderItem a_oFolder, CCourse a_oCourse)
		{
			Name = a_sFileName;

			Folder = a_oFolder;
			Course = a_oCourse;
		}

		//Deserialization constructor.
		public CFileItem(SerializationInfo a_oInfo, StreamingContext a_oContext)
		{
			Name = (String)a_oInfo.GetValue("Name", typeof(String));
			Download = (Boolean)a_oInfo.GetValue("Download", typeof(Boolean));
			Folder = (CFolderItem)a_oInfo.GetValue("Folder", typeof(CFolderItem));
			Course = (CCourse)a_oInfo.GetValue("Course", typeof(CCourse));
		}

		//Serialization function.
		public void GetObjectData(SerializationInfo a_oInfo, StreamingContext a_oContext)
		{
			a_oInfo.AddValue("Name", Name);
			a_oInfo.AddValue("Download", Download);
			a_oInfo.AddValue("Course", Course);
			a_oInfo.AddValue("Folder", Folder);
		}

		public override String ToString()
		{
			return Name;
		}
	}

	/// <summary>
	/// Contains information about the course.
	/// Basically, it has some properties, and a files & folders tree with all the course information
	/// The information for each TreeNode is saved in the TreeNode.Tag property
	/// </summary>
	[Serializable]
	public class CCourse : ISerializable
	{
		public String Name;
		public int Index;
		/// <summary>
		/// States if the data for this course has already been downloaded.
		/// </summary>
		public ERetrievedState DataRetrieved = ERetrievedState.NotRetrieved;

		// If this is false, the course has not been found on the highlearn server
		// Notice that this is a non-serialized parameter
		public bool CourseExists = false;

		/// <summary>
		/// States whether to download this course or not.
		/// </summary>
		public Boolean Download = false;

		public CFolderItem Data;

		public CCourse(String a_sName, int a_iIndex)
		{
			Name = a_sName;
			Index = a_iIndex;

			ClearData();
		}

		public static void CloneProperties(CCourse Original, CCourse Target)
		{
			Target.DataRetrieved = Original.DataRetrieved;
			Target.Download = Original.Download;
			Target.Index = Original.Index;
			Target.Name = Original.Name;
			Target.CourseExists = Original.CourseExists;
		}

		#region Serialization Functions
		//Deserialization constructor
		public CCourse(SerializationInfo a_oInfo, StreamingContext a_oContext)
		{
			Name = (String)a_oInfo.GetValue("Name", typeof(String));
			Index = (Int32)a_oInfo.GetValue("Index", typeof(Int32));
			DataRetrieved = (ERetrievedState)a_oInfo.GetValue("DataRetrieved", typeof(ERetrievedState));
			Data = (CFolderItem)a_oInfo.GetValue("Data", typeof(CFolderItem));
			CourseExists = false;

			if (DataRetrieved == ERetrievedState.Retrieved)
			{
				DataRetrieved = ERetrievedState.UpdateNeeded;
			}
		}
		//Serialization function
		public void GetObjectData(SerializationInfo a_oInfo, StreamingContext a_oContext)
		{
			a_oInfo.AddValue("Name", Name);
			a_oInfo.AddValue("Index", Index);
			a_oInfo.AddValue("DataRetrieved", DataRetrieved, typeof(ERetrievedState));
			a_oInfo.AddValue("Data", Data, typeof(CFolderItem));
		}
		#endregion

		public void ClearData()
		{
			Data = new CFolderItem("Main", null, this);
		}

		public override String ToString()
		{
			return Name;
		}

		private CFolderItem RecursiveGetFolderAt(String a_sPath, CFolderItem a_oFolder)
		{
			String l_sFolderName;
			int l_iBackslashIndex = a_sPath.IndexOf('\\');

			// The API_Highlearn doesn't use \\, but a /. So I've added a special case
			if (l_iBackslashIndex < 0)
			{
				l_iBackslashIndex = a_sPath.IndexOf('/');
			}

			if (a_sPath.Length > 0)
			{
				// First assign the folder name (will be changed if needed afterwards
				l_sFolderName = a_sPath;

				// If there are still more subdirectories to dive into
				if (l_iBackslashIndex > -1)
				{
					l_sFolderName = a_sPath.Substring(0, l_iBackslashIndex);
					
					// Remove that backslash
					a_sPath = a_sPath.Remove(l_iBackslashIndex, 1);
				}

				// Remove the current folder name
				a_sPath = a_sPath.Remove(0, l_sFolderName.Length);

				// And set out to the next subdirectory
				return RecursiveGetFolderAt(a_sPath, a_oFolder.Folders[l_sFolderName]);
			}
			else
			{
				// We're here
				return a_oFolder;
			}
		}

		public CFolderItem GetFolderAt(String a_sPath)
		{
			return RecursiveGetFolderAt(a_sPath, this.Data);
		}

		/// <summary>
		/// Counts all the files in the folder
		/// </summary>
		/// <param name="a_bFilterDownloadEnabled">Count only the files with the download flag enabled</param>
		/// <returns></returns>
		public int CountFiles(Boolean a_bFilterDownloadEnabled)
		{
			return Data.CountFiles(true, a_bFilterDownloadEnabled);
		}

	}

	[Serializable]
	public class CDataCenter : ISerializable
	{
		public delegate void CourseUpdatedDelegate(CCourse a_oFreshCourse);
		public delegate void CoursesListUpdatedDelegate();

		private SortedList<String, CCourse> m_oCourses;
		public SortedList<String, CCourse> Courses
		{
			get { return m_oCourses; }
			set { m_oCourses = value; }
		}

		private string m_sSaveFilename = "";
		public string SaveFilename
		{
			get { return m_sSaveFilename; }
			set { m_sSaveFilename = value; }
		}

		public bool m_bAllowSaving = false;
		public bool AllowSaving
		{
			get { return m_bAllowSaving; }
			set { m_bAllowSaving = value; }
		}

		public event CoursesListUpdatedDelegate OnCourseListUpdated;
		public event CourseUpdatedDelegate OnCourseUpdated;
		
		public CDataCenter()
		{
			m_oCourses = new SortedList<string, CCourse>();
		}

		#region Serialization Functions
		//Deserialization constructor.
		public CDataCenter(SerializationInfo a_oInfo, StreamingContext a_oContext)
		{
			//Get the values from info and assign them to the appropriate properties
			m_oCourses = (SortedList<String, CCourse>)a_oInfo.GetValue("Courses", typeof(SortedList<String, CCourse>));
		}

		//Serialization function.
		public void GetObjectData(SerializationInfo a_oInfo, StreamingContext a_oContext)
		{
			a_oInfo.AddValue("Courses", m_oCourses, typeof(SortedList<String, CCourse>));
		}
		#endregion

		private String ValidateDuplicateFilename(String a_sFilename, CFolderItem a_oFolder)
		{
			if (a_oFolder.Files.ContainsKey(a_sFilename))
			{
				return ValidateDuplicateFilename(a_sFilename, a_oFolder, 0);
			}
			
			return a_sFilename;
		}
		private String ValidateDuplicateFilename(String a_sFilename, CFolderItem a_oFolder, int a_iCounter)
		{
			if (a_oFolder.Files.ContainsKey(a_sFilename))
			{
				if (a_oFolder.Files.ContainsKey(a_sFilename + "_" + a_iCounter.ToString()))
				{
					return ValidateDuplicateFilename(a_sFilename, a_oFolder, ++a_iCounter);
				}

				return a_sFilename + "_" + a_iCounter.ToString();
			}

			return a_sFilename;
		}
		
		/// <summary>
		/// Checks if the file exists, and then checks its properties (Implemented the same as MakeFilename)
		/// If safe because it will simply return NULL if the file / path doesn't exist
		/// </summary>
		private CFileItem GetFileSafe(CFolderItem a_oFolder, List<String> a_asPath, String a_sFilename)
		{
			String l_sFolderName;
			CFolderItem l_oNewFolder;

			// Copy the path into a temp variable (we don't want to change the parameter)
			List<String> l_sPath = new List<String>(a_asPath);

			// Get to the folder, then find the file
			if (l_sPath.Count > 0)
			{
				l_sFolderName = l_sPath[0];
				l_sPath.RemoveAt(0);

				if (a_oFolder.Folders.ContainsKey(l_sFolderName))
				{
					l_oNewFolder = a_oFolder.Folders[l_sFolderName];
				}
				else
				{
					l_oNewFolder = new CFolderItem(l_sFolderName, a_oFolder, a_oFolder.Course);
					a_oFolder.Folders.Add(l_sFolderName, l_oNewFolder);
				}

				return GetFileSafe(l_oNewFolder, l_sPath, a_sFilename);
			}
			else
			{
				// We're here!

				if (a_oFolder.Files.ContainsKey(a_sFilename))
				{
					return a_oFolder.Files[a_sFilename];
				}
				return null;
			}
		}
	
		/// <summary>
		/// Creating a file withing the folder structure, creating all folders if needed
		/// </summary>
		/// <param name="a_oFolder"></param>
		/// <param name="a_sPath">A list of folders to create</param>
		/// <param name="a_sFilename"></param>
		private CFileItem MakeFilename(CFolderItem a_oFolder, List<String> a_asPath, String a_sFilename)
		{
			String l_sFolderName;
			CFolderItem l_oNewFolder;
			CFileItem l_oAddedFile;

			// Copy the path into a temp variable (we don't want to change the parameter)
			List<String> l_sPath = new List<String>(a_asPath);
	
			if (l_sPath.Count > 0)
			{
				l_sFolderName = l_sPath[0];
				l_sPath.RemoveAt(0);

				if (a_oFolder.Folders.ContainsKey(l_sFolderName))
				{
					l_oNewFolder = a_oFolder.Folders[l_sFolderName];
				}
				else
				{
					l_oNewFolder = new CFolderItem(l_sFolderName, a_oFolder, a_oFolder.Course);
					a_oFolder.Folders.Add(l_sFolderName, l_oNewFolder);
				}

				return MakeFilename(l_oNewFolder, l_sPath, a_sFilename);
			}
			else
			{
				// We're here!

				// Files with the same names are actually ok, GetProperFilename takes care of that
				a_sFilename = ValidateDuplicateFilename(a_sFilename, a_oFolder);
				
				l_oAddedFile = new CFileItem(a_sFilename, a_oFolder, a_oFolder.Course);
				a_oFolder.Files.Add(a_sFilename, l_oAddedFile);

				return l_oAddedFile;
			}
		}


		/// <summary>
		/// Validates the directory before saving
		/// </summary>
		private void ValidateConfigurationDirectory()
		{
			string l_sPath = Path.GetDirectoryName(m_sSaveFilename);
			if (!Directory.Exists(l_sPath))
			{
				Directory.CreateDirectory(l_sPath);
			}
		}

		// Serializes the datacenter to the SaveFilename file
		public void Save()
		{
			if (AllowSaving)
			{
				try
				{
					ValidateConfigurationDirectory();

					Stream l_oStream = File.Open(m_sSaveFilename, FileMode.Create);
					BinaryFormatter l_oBinFormatter = new BinaryFormatter();

					Logging.CLogger.LogMessage("Saving Downloaded Courses...");
					l_oBinFormatter.Serialize(l_oStream, this);
					l_oStream.Close();

					Logging.CLogger.LogMessage(" Done");
				}
				catch (Exception l_oException)
				{
					Logging.CLogger.LogMessage("Error Saving Courses - " + l_oException.Message);
				}
			}
		}

		// Removes old, non existing courses from the list
		public void CleanupOldCourses()
		{
			List<String> l_asRemovedCourses = new List<String> ();

			foreach (CCourse l_oCourse in Courses.Values)
			{
				if (!l_oCourse.CourseExists)
				{
					l_asRemovedCourses.Add(l_oCourse.Name);
				}
			}

			foreach (String l_sName in l_asRemovedCourses)
			{
				Courses.Remove(l_sName);
			}
		}

		#region Parsing Methods
		/// <summary>
		/// Parses the API_Highlearn data into the courses formation
		/// Used to parse the hashtable into the courses structure
		/// 
		/// The courses have index, but if a course is added the indexes sometimes change, so updating is a must here.
		/// </summary>
		/// <param name="a_oHash"></param>
		public void ParseCoursesList(System.Collections.Hashtable a_oHash)
		{
			int l_iIndex = 0;
			foreach (string l_sKey in a_oHash.Keys)
			{
				Hashtable node_folder = (Hashtable)a_oHash[l_sKey];
				l_iIndex = (int)node_folder["_index"];

				if (!m_oCourses.ContainsKey(l_sKey))
				{
					// Key = Course Name
					// Index = Course Number

					m_oCourses.Add(l_sKey, new CCourse(l_sKey, l_iIndex));
				}
				else
				{
					m_oCourses[l_sKey].Index = l_iIndex;
				}

				m_oCourses[l_sKey].CourseExists = true;
			}

			// Notify the user
			if (OnCourseListUpdated != null)
				OnCourseListUpdated();

			Save();
		}
	
// 		private void ParseInnerFolder(CFolderItem a_oFolder, Hashtable a_oHash)
// 		{
// 			Hashtable l_oHashFolder;
// 			CFolderItem l_oNewFolder;
// 
// 			foreach (string Key in a_oHash.Keys)
// 			{
// 				// Ignore the first index folder
// 				if (Key == "_index")
// 					continue;
// 
// 				l_oHashFolder = (Hashtable)a_oHash[Key];
// 			
// 				l_oNewFolder = new CFolderItem(Key);
// 				a_oFolder.Folders.Add(Key, l_oNewFolder);
// 				
// 				ParseInnerFolder(l_oNewFolder, l_oHashFolder);
// 			}
// 		}

		/// <summary>
		/// Merges new data with an existing course in the DataCenter
		/// </summary>
		/// <param name="a_sCourseName"></param>
		/// <param name="a_oItems"></param>
		public void ParseFolders(String a_sCourseName, List<Miner_Highlearn.HLItem> a_oItems)
		{
			List<String> l_oSplittedFolders;

			CCourse l_oOriginalCourse = m_oCourses[a_sCourseName];
			CCourse l_oNewCourse = new CCourse(a_sCourseName, l_oOriginalCourse.Index);

			CFileItem l_oReplacedFile;
			CFileItem l_oNewFile;

			// For debug & testing purposes
			//TestingUtilites.SaveHLItems(a_oItems, "Dump.dump");

			// First create an empty course
			l_oNewCourse.ClearData();
			CCourse.CloneProperties(l_oOriginalCourse, l_oNewCourse);

			foreach (Miner_Highlearn.HLItem l_oItem in a_oItems)
			{
				// Split the folder names
				l_oSplittedFolders = new List<String>();
				l_oSplittedFolders.AddRange(l_oItem.Path.Split('/'));

				// And start recursing - Adding the new downloaded files
				l_oNewFile = MakeFilename(l_oNewCourse.Data, l_oSplittedFolders, l_oItem.Name);

				// Check if the file already exists in the refreshed overridden course. If so, we need to take its download state from it.
				l_oReplacedFile = GetFileSafe(l_oOriginalCourse.Data, l_oSplittedFolders, l_oItem.Name);
				if (l_oReplacedFile != null)
				{
					l_oNewFile.Download = l_oReplacedFile.Download;
				}
			}

			// Now replace the old course with the new one
			m_oCourses[a_sCourseName] = l_oNewCourse;
			m_oCourses[a_sCourseName].DataRetrieved = ERetrievedState.Retrieved;

			// Notify the user
			if (OnCourseUpdated != null)
				OnCourseUpdated(m_oCourses[a_sCourseName]);

			Save();
		}

		public void ParseAll(System.Collections.Hashtable a_oHash)
		{
			// parses all the tree (From GetCourseInformation)
		}
		#endregion
	}
}
